<template>
  <div class="number-range-container">
    <div
      :id="usePrepend ? 'prepend' : ''"
      :class="{
        'slot-default': slotStyle === 'default',
        'slot-pend ': usePrepend,
      }"
    >
      <slot name="prepend">
        <!-- 前缀插槽 -->
      </slot>
    </div>
    <div
      class="number-range"
      :class="{
        'is-disabled': disabled,
        'is-focus': isFocus,
        'number-range-left-border-radius-0': usePrepend,
        'number-range-right-border-radius-0': useAppend,
      }"
    >
      <el-input-number
        :disabled="disabled"
        placeholder="最小值"
        @blur="handleBlur"
        @focus="handleFocus"
        @change="handleChangeMinValue"
        v-model="minValue_"
        v-bind="$attrs"
        @update:modelValue="(val: number) => $emit('update:minValue', val)"
        :controls="false"
        :min="null"
      />
      <div class="to">
        <span>{{ to }}</span>
      </div>
      <el-input-number
        :disabled="disabled"
        placeholder="最大值"
        @blur="handleBlur"
        @focus="handleFocus"
        @change="handleChangeMaxValue"
        v-model="maxValue_"
        v-bind="$attrs"
        @update:modelValue="(val: number) => $emit('update:maxValue', val)"
        :controls="false"
        :min="null"
      />
    </div>
    <div
      :id="useAppend ? 'append' : ''"
      :class="{
        'slot-default': slotStyle === 'default',
        'slot-pend ': useAppend,
      }"
    >
      <slot name="append">
        <!-- 后缀插槽 -->
      </slot>
    </div>
  </div>
</template>
<script lang="ts" setup name="numberRange">
import { computed } from "vue";

const props = defineProps({
  modelValue: {
    type: Array,
    default: () => [null, null], // 调用时使用v-model="[min,max]" 绑定
  },
  minValue: {
    type: [Number, null],
    default: null, // 调用时使用v-model:min-value="" 绑定多个v-model
  },
  maxValue: {
    type: [Number, null],
    default: null, // 调用时使用v-model:max-value="" 绑定多个v-model
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false,
  },
  to: {
    type: String,
    default: "至",
  },
  // 精度参数 -保留小数位数
  precision: {
    type: Number,
    default: 0,
    validator(val: number) {
      return val >= 0 && val === parseInt(String(val), 10);
    },
  },
  // 限制取值范围
  valueRange: {
    type: Array,
    default: () => [],
    validator(val: []) {
      if (val && val.length > 0) {
        // @ts-ignore
        if (val.length !== 2) {
          throw new Error("请传入长度为2的Number数组");
        }
        // @ts-ignore
        if (typeof val[0] !== "number" || typeof val[1] !== "number") {
          throw new Error("取值范围只接受Number类型,请确认");
        }
        // @ts-ignore
        if (val[1] < val[0]) {
          throw new Error("valueRange格式须为[最小值,最大值],请确认");
        }
      }
      return true;
    },
  },
  // 插槽样式
  slotStyle: {
    type: String, // default --异色背景 |  plain--无背景色
    default: "default",
  },
});

const emit = defineEmits([
  "update:modelValue",
  "update:minValue",
  "update:maxValue",
  "change",
]);

const minValue_ = computed({
  get() {
    return props.minValue ?? props.modelValue[0] ?? null;
  },
  set(value) {
    emit("update:minValue", value);
    emit("update:modelValue", [value, maxValue_.value]);
  },
});

const maxValue_ = computed({
  get() {
    return props.maxValue ?? props.modelValue[1] ?? null;
  },
  set(value) {
    emit("update:maxValue", value);
    emit("update:modelValue", [minValue_.value, value]);
  },
});

// 最小值输入框变化
const handleChangeMinValue = (value: number) => {
  // 当值为空或非数字时返回null
  if (value === null || value === undefined || isNaN(value)) {
    emit("update:minValue", null);
    emit("update:modelValue", [null, maxValue_.value]);
    return;
  }
  // 初始化数字精度
  let newMinValue = parsePrecision(value, props.precision);

  // 进行范围限制
  if (props.valueRange && props.valueRange.length > 0) {
    const [minRange, maxRange] = props.valueRange as [number, number];
    newMinValue = Math.max(minRange, Math.min(newMinValue, maxRange));
  }

  // min > max 且 max 不为空时才交换
  if (
    typeof newMinValue === "number" &&
    maxValue_.value !== null &&
    parseFloat(String(newMinValue)) > parseFloat(String(maxValue_.value))
  ) {
    const { min, max } = decideValueRange(Number(maxValue_.value), newMinValue);
    updateValue(min, max);
  } else {
    // 如果max为空，只更新min值
    if (maxValue_.value === null) {
      updateValue(newMinValue, null);
    } else {
      const { min, max } = decideValueRange(
        newMinValue,
        Number(maxValue_.value)
      );
      updateValue(min, max);
    }
  }
};

// 最大值输入框变化
const handleChangeMaxValue = (value: number) => {
  // 当值为空或非数字时返回null
  if (value === null || value === undefined || isNaN(value)) {
    emit("update:maxValue", null);
    emit("update:modelValue", [minValue_.value, null]);
    return;
  }
  // 初始化数字精度
  let newMaxValue = parsePrecision(value, props.precision);
  // 进行范围限制
  if (props.valueRange && props.valueRange.length > 0) {
    const [minRange, maxRange] = props.valueRange as [number, number];
    newMaxValue = Math.max(minRange, Math.min(newMaxValue, maxRange));
  }
  // max < min 且 min 不为空时才交换
  if (
    typeof newMaxValue === "number" &&
    minValue_.value !== null &&
    parseFloat(String(newMaxValue)) < parseFloat(String(minValue_.value))
  ) {
    const { min, max } = decideValueRange(newMaxValue, Number(minValue_.value));
    updateValue(min, max);
  } else {
    // 如果min为空，只更新max值
    if (minValue_.value === null) {
      updateValue(null, newMaxValue);
    } else {
      const { min, max } = decideValueRange(
        Number(minValue_.value),
        newMaxValue
      );
      updateValue(min, max);
    }
  }
};

// 更新数据
const updateValue = (min: number | null, max: number | null) => {
  emit("update:minValue", min);
  emit("update:maxValue", max);
  emit("update:modelValue", [min, max]);
  emit("change", { min, max });
};

// 取值范围判定
const decideValueRange = (min: number, max: number) => {
  if (props.valueRange && props.valueRange.length > 0) {
    const [minRange, maxRange] = props.valueRange as [number, number];
    min = Math.min(Math.max(min, minRange), maxRange);
    max = Math.min(max, maxRange);
  }
  return { min, max };
};

// input焦点事件
const isFocus = ref();

const handleFocus = () => {
  isFocus.value = true;
};

const handleBlur = () => {
  isFocus.value = false;
};

// 处理数字精度
const parsePrecision = (number: number, precision = 0) => {
  return parseFloat(
    String(
      Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision)
    )
  );
};

// 判断插槽是否被使用
// 组件外部使用时插入了
// <template #插槽名 >
// </template>
// 无论template标签内是否插入了内容，均视为已使用该插槽
const slots = useSlots();
const usePrepend = computed(() => {
  // 前缀插槽
  return slots && slots.prepend ? true : false;
});
const useAppend = computed(() => {
  // 后缀插槽
  return slots && slots.append ? true : false;
});
</script>
<style lang="scss" scoped>
.number-range-container {
  display: flex;
  height: 100%;
  .slot-pend {
    white-space: nowrap;
    color: var(--el-color-info);
    border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  }
  #prepend {
    padding: 0 20px;
    box-shadow: 1px 0 0 0 var(--el-input-border-color, var(--el-border-color))
        inset,
      0 1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
      0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
    border-right: 0;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
  }
  #append {
    padding: 0 15px;
    box-shadow: 0 1px 0 0 var(--el-input-border-color, var(--el-border-color))
        inset,
      0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
      -1px 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
    border-left: 0;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
  }
  .slot-default {
    background-color: var(--el-fill-color-light);
  }

  .number-range-left-border-radius-0 {
    border-top-left-radius: 0 !important;
    border-bottom-left-radius: 0 !important;
  }
  .number-range-right-border-radius-0 {
    border-top-right-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
  }

  .number-range {
    background-color: var(--el-bg-color) !important;
    box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color))
      inset;
    border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
    padding: 0 2px;
    display: flex;
    flex-direction: row;
    width: 100%;
    justify-content: center;
    align-items: center;
    color: var(--el-input-text-color, var(--el-text-color-regular));
    transition: var(--el-transition-box-shadow);
    transform: translate3d(0, 0, 0);
    overflow: hidden;

    .to {
      margin-top: 1px;
    }
  }

  .is-focus {
    transition: all 0.3s;
    box-shadow: 0 0 0 1px var(--el-color-primary) inset !important;
  }
  .is-disabled {
    background-color: var(--el-input-bg-color);
    color: var(--el-input-text-color, var(--el-text-color-regular));
    cursor: not-allowed;
    .to {
      height: calc(100% - 3px);
      background-color: var(--el-fill-color-light) !important;
    }
  }
}

:deep(.el-input) {
  border: none;
}
:deep(.el-input__wrapper) {
  margin: 0;
  padding: 0 15px;
  background-color: transparent;
  border: none !important;
  box-shadow: none !important;
  &.is-focus {
    border: none !important;
    box-shadow: none !important;
  }
}
</style>
